require "inifile.rb"

module Saveload

#Save is the savegame file format. Save files are zipfiles composed of a header (save.svh) ad several content files.
#The header contains the following:
# * The version of the game engine which wrote the savefile
# * The version of the savefile format
# * A date
# * A timestamp, as a unique ID
# * A random key (an authkey) as a secondary ID
# * A list of the contained components and their paths
#
#The rest of a save file is composed of Components. A Component is a single game object which can be saved. It has both a save and a load method.
class Save
  SaveVersion = "pre alpha <0.01[CS]"
  SaveVersMajor = 0
  SaveVersMinor = 0.01
  
  #A Component is a single potion of a save file which handles its own saving and loading.
  #With components, savegames are modularlized so that different parts of the game can be saved and others dropped.
  class Component
    #Procedure to save the component
    #file is the target file
    def save(file)
    #  return true
    end
    #Procedure to load the component
    #file is the target file
    def load(file)
    #  return true
    end
  end
  
  class TileList < Component
    def save(file)
      $window.deleteAllWidgets
      file=File.new(file,"w") 
      Marshal.dump(Tile.getAllInstances, file)
      file.close
      $window.createWidgets
    end
    def load(file)
      Tile.deleteAllInstances
      file=File.new(file,"w") 
      Tile.setAllInstances(Marshal.load(file))
      file.close
    end
  end
  
  class Player < Component
    def save(file)
      file=File.new(file,"w") 
      Marshal.dump($window.player, file)
      file.close
    end
    def load(file)
      file=File.new(file,"w") 
      $window.player=Marshal.load(file)
      file.close
    end
  end
  
  class NPCs < Component
    def save(file)
      file=File.new(file,"w") 
      Marshal.dump($window.characters, file)
      file.close
    end
    def load(file)
      file=File.new(file,"w") 
      $window.characters=Marshal.load(file)
      file.close
    end
  end
  
  class NodeMap < Component
    def save(file)
      file=File.new(file,"w") 
      Marshal.dump($window.map.nodeMap.nodes, file)
      file.close
    end
    def load(file)
      file=File.new(file,"w") 
      $window.map.nodeMap.nodes=Marshal.load(file)
      file.close
    end
  end
  
  class ColMap < Component
    def save(file)
      file=File.new(file,"w") 
      Marshal.dump($window.map.collisionMap.pixels, file)
      file.close
    end
    def load(file)
      file=File.new(file,"w") 
      $window.map.collisionMap.pixels=Marshal.load(file)
      file.close
    end
  end
  
  class ScriptStore < Component
    def save(file)
      File.copy(RubyScript::ScriptUtilities::MemStore, file)
      File.copy(RubyScript::ScriptUtilities::OMemStore, file+"o.dat")
      
      File.copy(SpeechScript::MemStore, file)
      File.copy(SpeechScript::OMemStore, file+"o.dat")
    end
    def load(file)
      File.delete(RubyScript::ScriptUtilities::MemStore)
      File.copy(file, RubyScript::ScriptUtilities::MemStore)
      File.delete(RubyScript::ScriptUtilities::OMemStore)
      File.copy(file+"o.dat", RubyScript::ScriptUtilities::OMemStore)
      
      File.delete(SpeechScript::MemStore)
      File.copy(file, SpeechScript::MemStore)
      File.delete(SpeechScript::OMemStore)
      File.copy(file+"o.dat", SpeechScript::OMemStore)
    end
  end
  
  class RScriptStore < Component
    def save(file)
      File.copy(RubyScript::ScriptUtilities::MemStore, file)
      File.copy(RubyScript::ScriptUtilities::OMemStore, file+"o.dat")
    end
    def load(file)
      File.delete(RubyScript::ScriptUtilities::MemStore)
      File.copy(file, RubyScript::ScriptUtilities::MemStore)
      File.delete(RubyScript::ScriptUtilities::OMemStore)
      File.copy(file+"o.dat", RubyScript::ScriptUtilities::OMemStore)
    end
  end
  
  class SScriptStore < Component
    def save(file)
      File.copy(SpeechScript::MemStore, file)
      File.copy(SpeechScript::OMemStore, file+"o.dat")
    end
    def load(file)
      File.delete(SpeechScript::MemStore)
      File.copy(file, SpeechScript::MemStore)
      File.delete(SpeechScript::OMemStore)
      File.copy(file+"o.dat", SpeechScript::OMemStore)
    end
  end
  
  class MapVars < Component
    def save(file)
      File.copy("temp/bump.def", file)
      File.copy("temp/map.obj", file)
      File.copy("temp/mappathfinding.obj", file)
      File.copy("temp/initmap.script", file)
    end
    def load(file)
      File.delete("temp/bump.def")
      File.delete("temp/map.obj")
      File.delete("temp/mappathfinding.obj")
      File.delete("temp/mappathfinding.obj")
      File.copy(file, "temp/bump.def")
      File.copy(file, "temp/map.obj")
      File.copy(file, "temp/mappathfinding.obj")
      File.copy(file, "temp/initmap.script")
    end
  end

  class MapGFX < Component
    def save(file)
      File.copy("temp/mapbg.png", file)
      File.copy("temp/bump.png", file)
    end
    def load(file)
      File.delete("temp/mapbg.png")
      File.delete("temp/bump.png")
      File.copy(file, "temp/mapbg.png")
      File.copy(file, "temp/bump.png")
    end
  end
  
  class MapScripts < Component
    
  end
  
=begin
  class Marshaled < Component
    def save(file)
      file=File.new(file+"playerdata.bin","w") 
      file.close
      file.reopen(file+"npcdata.bin","w") 
      file.close
    end
    def load(file)
      file=File.new(file+"playerdata.bin","w") 
      file.close
    end
  end
=end
  
  class PlayerTraits < Component
    def save(file)
      curtraits=Datafile.new("temp/runtime/vars/playertraits.def").each(1) { |arg|
        if arg!=nil
          File.copy("temp/runtime/vars/"+arg,file+arg)
        end
      }
      File.copy("temp/runtime/vars/playertraits.def", file+"index.def")
    end
    def load(file)
      File.copy(file+"index.def", "temp/runtime/vars/playertraits.def")
      curtraits=Datafile.new("temp/runtime/vars/playertraits.def").each(1) { |arg|
        if arg!=nil
          File.copy(file+arg, "temp/runtime/vars/"+arg)
        end
      }
    end
  end
  
  module Components
    TileList=TileList.new
    Player=Saveload::Save::Player.new
    NPCs=NPCs.new
    NodeMap=Saveload::Save::NodeMap.new
    ColMap=ColMap.new
    ScriptStore=ScriptStore.new
    RScriptStore=RScriptStore.new
    SScriptStore=SScriptStore.new
    MapVars=MapVars.new
    MapGFX=MapGFX.new
    PlayerTraits=PlayerTraits.new
    #MapScripts=10
  end
  
  ComponentMap = { 
    "TileList" => Components::TileList,
    "Player" => Components::Player,
    "NPCs" => Components::NPCs,
    "NodeMap" => Components::NodeMap,
    "CollisionMap" => Components::ColMap,
    "ScriptStore" => Components::ScriptStore,
    "RScriptStore" => Components::RScriptStore,
    "SScriptStore" => Components::SScriptStore,
    "MapProperties" => Components::MapVars,
    "MapGFX" => Components::MapGFX,
    "PlayerTraits" => Components::PlayerTraits
    #"MapScripts" => Component::MapScripts,
  }
  
  attr_accessor :file, :name, :components
  
  def initialize(file, name)
    @file=file
    @name=name
    @components={}
  end
  
  #Add component name to the path path
  def addComponent(name,path=name)
    @components[name]=path
  end
  
  def Save.clearWorkDir
    Dir.new("temp/saves/").each { |file|
      File.delete("temp/saves/"+file) if File.file?("temp/saves/"+file)
    }
  end
  
  #Write the savefile
  def write
  
    Save.clearWorkDir
    
    save = IniFile.new #save header file
    t = Time.new
    stamp = ((t.mon+1)*(t.hour+1)*(t.min+1)*(t.sec+1)).to_s(16).upcase
    save["HEADER"] = { 
      "GameVers" => "pre alpha <0.0005", 
      "SaveVers" => SaveVersion, 
      "SaveVersMaj" => SaveVersMajor,
      "SaveVersMin" => SaveVersMinor,
      "Date" => t.to_s, 
      "TimeStamp" => "0x"+stamp,
      "AuthKey"=> !stamp
    }
    
    save["Metadata"] = {
      "Name" => @name
    }
    
    save["Player"] = {
      "IntName" => $window.player.name
    }
    
    comp={}
    @components.each { |name,loc|
      comp[name]=loc
    }
    
    save["Components"]=comp
    
    save["FOOTER"] = {
      "CreationTime" => (Time.new-t).to_s+"s",
      "ENDFILE" => "TRUE"
    }
    
    save.write("temp/save.svh")
  
    @components.each { |name,loc|
      ComponentMap[name].save("temp/saves/"+loc)
    }
    
    #resources (animations, audio, etc) and widgets not saved
    
    #Zip file
    
    Zip::ZipFile.open(@file,true) { |zipfile|
      Dir.new("temp/saves/").each { |file|
        zipfile.add(file, "temp/saves/"+file)
      }
      zipfile.add("save.svh", "temp/save.svh")
    }
    
    Save.clearWorkDir
    
  end
  
  #Load the savefile
  def load(file=@file)
    
    Save.clearWorkDir
    
  #  puts "here"
  #  STDIN.gets
 #   if not File.exists?("saves/"+mkPath(file)+".sav")
 #     return false
 #   end
    revmap={}
    begin
    File.copy("saves/"+mkPath(file)+".sav","temp/save.sav")
    rescue
    File.copy(file,"temp/save.sav")
    end
    Zip::ZipFile.open { |zipfile|
      zipfile.extract("save.svh","temp/save.svh")
      save=Inifile.new(File.new("temp/save.svh"))
      if save["HEADER"]["SaveVersMaj"]==SaveVersMajor
        if save["HEADER"]["SaveVersMin"]!=SaveVersMinor
          $flog.warn("Load Save") {file+" is not of a 100% compatible save version. Load will be attempted"}
          $flog.info("Load Save") {"File save version: "+save["HEADER"]["SaveVers"]}
          $flog.info("Load Save") {"Game save version: "+SaveVersion}
        end
      else
        $flog.error("Load Save") {file+" is of an incompatible save version. Aborting load"}
        $flog.info("Load Save") {"File save version: "+save["HEADER"]["SaveVers"]}
        $flog.info("Load Save") {"Game save version: "+SaveVersion}
        return
      end
      save["Components"].each { |key,loc|
        zipfile.extract(loc,"temp/saves/"+loc)
        revmap[loc]=key
      }
    }
    
    Dir.new("temp/saves").each { |file|
      ComponentMap[revmap[file]].load("temp/saves/"+file)
    }
    
    File.delete("temp/save.sav")

    Save.clearWorkDir

  end

  #Make a savefile path for use with the in-game save GUI
  def mkPath(name)
    name.sub!(" ","_")
    #name.slice!(0,8)
    return name
  end

end

end #module Saveload

include Saveload

=begin
make file
put header - done
put player pos
put character positions
put item positions
put campaign variables
put options
put footer
compress
=end